#include <SFML/Graphics.hpp>
#include <iostream>
#include <string>
#include <boost/lexical_cast.hpp>

using namespace std;

int main(int num_args, char* cl_args[])
{
   //config stuff
   int width = 1200;
   int height = 600;
   int rule = 30;
   double zoom = 1.0;

   int temprule;
   double tempzoom;
   bool arg_error = false;

   //command line argument stuff
   if (num_args == 2)
   {
      temprule = atoi(cl_args[1]);
      (temprule >= 0 && temprule <= 255) ? (rule = temprule) : (arg_error = true); //set the rule equal to the cli arg
   }
   else if (num_args == 3)
   {
      temprule = atoi(cl_args[1]);
      tempzoom = atof(cl_args[2]);
      if (temprule >= 0 && temprule <= 255 && tempzoom >= 1.0) //set rule and zoom equal to the cli args
      {
         rule = temprule;
         zoom = tempzoom;
      }
      else
      {
         arg_error = true;
      }
   }
   //error condition
   if ((num_args != 2 && num_args != 3) || arg_error)
   {
      cout
               << "Syntax is\nECAutomata [rule number] [zoom amount(a float)]\nExample: ECAutomata 15 6.8"
               << endl;
      cout
               << "zoom is optional, you can also do just the rule\nexample: ECAutomata 30"
               << endl;
      cout << "The zoom level should be 1 or greater, 1 will give you no zoom"
               << endl;
      cout << "The rule value should be between 0 and 255" << endl;
      return 0;
   }

   string rule_string = "Rule: " + boost::lexical_cast<string>(rule);

   sf::Color bgcolor = sf::Color::White;
   sf::Color fgcolor = sf::Color::Black;
   sf::Color txtcolor = sf::Color(255, 0, 55);

   sf::Image img;
   img.Create(width, height, bgcolor);
   img.SetSmooth(false);  //this needs to be false or the image is blurry

   // Create main window
   sf::RenderWindow App(sf::VideoMode(width, height),
                        "Elementary Cellular Automata " + rule_string);

   //we make our array a little bit too large to avoid null pointer exceptions later
   bool cells[width + 2][height + 2];
   for (int i = 0; i < width + 2; i++)
   {
      for (int j = 0; j < height + 2; j++)
      {
         cells[i][j] = false;
      }
   }

   //initial pixel(s)
   int middle_pix = width / 2;
   cells[middle_pix][0] = true;
   img.SetPixel(middle_pix, 0, fgcolor);
   int rulelist[8];

   for (int j = 0; j < 8; j++)
   {
      rulelist[j] = (pow(2, 7 - j)); // this comes out to 128, 64, 32, 16, etc...
   }

   //set all of the bits to false as their initial value
   bool bits[8];
   for (int i = 0; i < 8; i++)
   {
      bits[i] = false;
   }
   //main loop!
   for (int y = 1; y < height; y++)
   {
      for (int x = 1; x < width; x++)
      {

         //decode the rule number
         while (rule >= 1)
         {
            for (int n = 0; n < 8; n++)
            {
               if (rule >= rulelist[n])
               {
                  rule -= rulelist[n];
                  bits[7 - n] = true;
               }
            }
         }

         //wrap around array
         int z = x;
         if (x == 1)
         {
            z = width - 2;
         }
         else if (x == width - 1)
         {
            z = 2;
         }

         //condition checks + drawing code
         bool case1[8] = { false, false, false, false, true, true, true, true };
         bool case2[8] = { false, false, true, true, false, false, true, true };
         bool case3[8] = { false, true, false, true, false, true, false, true };

         for (int c = 0; c < 8; c++)
         {
            //this is a 3 pixel wide array that does different things to the current pixel depending on the rule bits.
            if ((cells[z - 1][y - 1] == case1[c] && cells[x][y - 1] == case2[c]
                     && cells[z + 1][y - 1] == case3[c]) && bits[c])
            {
               cells[x][y] = true;
               img.SetPixel(x, y, fgcolor);
            }
         }
      }
   }

   //This stuff is boilerplate SFML
   // Start game loop
   while (App.IsOpened())
   {
      // Process events
      sf::Event Event;
      while (App.GetEvent(Event))
      {
         // Close window : exit
         if (Event.Type == sf::Event::Closed)
            App.Close();
      }

      // DRAW code
      App.Clear(sf::Color::Black);
      sf::Sprite Sprite;
      Sprite.SetImage(img);
      Sprite.Scale(zoom, zoom);
      Sprite.Move(0.5 * (width - width * zoom), 0);
      App.Draw(Sprite);
      App.Display(); //Display rendered scene
   }
   return EXIT_SUCCESS;
}
